#include "NES.h"
#include <stdlib.h>
#include "SDL.h"

////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char SingletonReadMemory( unsigned short address ) {
    return NES::GetInstance()->ReadMemory( address );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void SingletonWriteMemory( unsigned short address, unsigned char value ) {
    NES::GetInstance()->WriteMemory( address, value );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

NES* NES::m_Instance = NULL;

////////////////////////////////////////////////////////////////////////////////////////////////////

NES* NES::GetInstance() {
    if( m_Instance == NULL ) m_Instance = new NES();

    return m_Instance;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

NES::NES() {
    m_RAM = ( unsigned char* )malloc( 0x10000 );
    m_VRAM = ( unsigned char* )malloc( 0x4000 );
    m_SRAM = ( unsigned char* )malloc( 0x100 );

    memset( m_RAM, 0, 0x10000 );
    memset( m_VRAM, 0, 0x4000 );
    memset( m_SRAM, 0, 0x100 );

    m_6502 = new M6502();
    m_6502->SetRead( SingletonReadMemory );
    m_6502->SetWrite( SingletonWriteMemory );

    m_PPU = new PPU();
    m_PPU->SetVRAM( m_VRAM );
    m_PPU->SetSRAM( m_SRAM );

    m_Joypad_1 = new Joypad();

    m_Running = false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

NES::~NES() {
    SDL_FreeSurface( m_Screen );
    SDL_Quit();

    free( m_RAM );
    free( m_VRAM );
    free( m_SRAM );

    delete( m_6502 );
    delete( m_PPU );
    delete( m_Joypad_1 );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void NES::LoadCart( const std::string &path ) {
    FILE* fp = fopen( path.c_str(), "rb" );

    if( !fp ) {
        printf( "\nERROR: no se pudo abrir el archivo < %s >", path.c_str() );
        getchar();
        exit( 0 );
    }

    // bancos de PRG-ROM
    fseek( fp, 4, 0 );
    int rom_banks = fgetc( fp );

    // mirroring
    fseek( fp, 6, 0 );
    int mirroring = fgetc( fp );
    if( mirroring == 1 ) m_PPU->SetMirroring( VERTICAL );
    else                 m_PPU->SetMirroring( HORIZONTAL );

    // cargar bancos de PRG-ROM
    fseek( fp, 16, 0 );
    if( rom_banks == 1 ) {
        fread( &m_RAM[0x8000], 1, 0x4000, fp );
        memcpy( &m_RAM[0xc000], &m_RAM[0x8000], 0x4000 );
    } else if( rom_banks == 2 ) {
        fread( &m_RAM[0x8000], 1, 0x8000, fp );
    } else {
        fclose( fp );
        printf("\nERROR: demasiados bancos de memoria PRG-ROM");
        getchar();
        exit( 0 );
    }

    // cargar bancos de CHR-ROM
    fread( m_VRAM, 1, 0x2000, fp );

    fclose( fp );

    unsigned char h = m_RAM[ 0xfffd ];
    unsigned char l = m_RAM[ 0xfffc ];
    m_6502->SetPC( ((h << 8) | l) & 0xffff );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void NES::LaunchGraphics() {
    SDL_Init( SDL_INIT_EVERYTHING );
    SDL_WM_SetCaption( "nnl-nes", NULL );
    m_Screen = SDL_SetVideoMode( 512, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char NES::ReadMemory( unsigned short address )const {
    address &= 0xffff;

    // RAM
    if( address < 0x2000 ) {
        return m_RAM[ address & 0x07ff ];
    }

    // PPU
    else if( address < 0x4000 ) {
        return m_PPU->ReadRegister( address & 0x0007 );
    }

    // joypad #1
    else if( address == 0x4016 ) {
        m_RAM[ address ] = m_Joypad_1->Strobe();
    }

    // otra region...
    // FIXME: PRG-ROM tal vez? segun nes_emu.txt:2264 esta region es un mirror de (0..0x3fff)
    else {
        return m_RAM[ address ];
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void NES::WriteMemory( unsigned short address, unsigned char value ) {
    address &= 0xffff;
    value &= 0xff;

    // RAM
    if( address < 0x2000 ) {
        m_RAM[ address & 0x07ff ] = value;
    }

    // registros de la PPU
    else if( address < 0x4000 ) {
        m_PPU->WriteRegister( address & 0x0007, value );
    }
    
    // sprite DMA
    else if( address == 0x4014 ) {
        std::list<unsigned char> data;
        for( unsigned short i = 0; i < 0x0100; i++ ) data.push_back( ReadMemory((0x0100 * value + i) & 0xffff) );
        m_PPU->WriteSpriteDMA( data );
    }

    // joypad #1
    else if( address == 0x4016 ) {
        m_RAM[ address ] = 0x40;
    }

    // otra region...
    // FIXME: PRG-ROM tal vez? segun nes_emu.txt:2264 esta region es un mirror de (0..0x3fff)
    else {
        m_RAM[ address ] = value;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void NES::Start() {
    LaunchGraphics();
    SDL_Event e;

    m_Running = true;
    while( m_Running ) {
        int cycles = m_6502->Execute( m_6502->Fetch() );
        m_PPU->Run( cycles );

        // NMI
        if( m_PPU->WantNMI() ) {
            m_PPU->ToggleNMI();
            m_6502->HandleNMI();
        }

        // FIXME
        while( SDL_PollEvent(&e) ) {
            switch( e.type ) {
                case SDL_KEYDOWN: {
                    if( e.key.keysym.sym == SDLK_ESCAPE ) m_Running = false;

                    // joypad #1
                    if( e.key.keysym.sym == SDLK_t )      m_Joypad_1->SetInput( BUTTON_A );
                    if( e.key.keysym.sym == SDLK_y )      m_Joypad_1->SetInput( BUTTON_B );
                    if( e.key.keysym.sym == SDLK_LSHIFT ) m_Joypad_1->SetInput( BUTTON_SELECT );
                    if( e.key.keysym.sym == SDLK_LCTRL )  m_Joypad_1->SetInput( BUTTON_START );
                    if( e.key.keysym.sym == SDLK_w )      m_Joypad_1->SetInput( BUTTON_UP );
                    if( e.key.keysym.sym == SDLK_s )      m_Joypad_1->SetInput( BUTTON_DOWN );
                    if( e.key.keysym.sym == SDLK_a )      m_Joypad_1->SetInput( BUTTON_LEFT );
                    if( e.key.keysym.sym == SDLK_d )      m_Joypad_1->SetInput( BUTTON_RIGHT );
                } break;

                case SDL_KEYUP: {
                    if( e.key.keysym.sym == SDLK_ESCAPE ) m_Running = false;

                    // joypad #1
                    if( e.key.keysym.sym == SDLK_t )      m_Joypad_1->ClearInput( BUTTON_A );
                    if( e.key.keysym.sym == SDLK_y )      m_Joypad_1->ClearInput( BUTTON_B );
                    if( e.key.keysym.sym == SDLK_LSHIFT ) m_Joypad_1->ClearInput( BUTTON_SELECT );
                    if( e.key.keysym.sym == SDLK_LCTRL )  m_Joypad_1->ClearInput( BUTTON_START );
                    if( e.key.keysym.sym == SDLK_w )      m_Joypad_1->ClearInput( BUTTON_UP );
                    if( e.key.keysym.sym == SDLK_s )      m_Joypad_1->ClearInput( BUTTON_DOWN );
                    if( e.key.keysym.sym == SDLK_a )      m_Joypad_1->ClearInput( BUTTON_LEFT );
                    if( e.key.keysym.sym == SDLK_d )      m_Joypad_1->ClearInput( BUTTON_RIGHT );
                } break;
            }
        }
        
        // FIXME
        if( m_PPU->WantRender() ) {
            Render( m_PPU->GetScreenData() );
        }
    }
}

/*void NES::Start() {
    m_Running = true;
    int cycles = 0;

    LaunchGraphics();
    SDL_Event e;
    
    int cpu_timming[] = { 114, 114, 113 };
    int t = 0;

    while( m_Running ) {
        int target = cpu_timming[t++ % 3];
        while( cycles <= target ) cycles += m_6502->Execute( m_6502->Fetch() );
        cycles -= target;

        // scanlines
        m_PPU->RunScanline();

        // NMI
        if( m_PPU->WantNMI() ) {
            m_PPU->ToggleNMI();
            m_6502->HandleNMI();
        }

        // FIXME
        while( SDL_PollEvent(&e) ) {
            switch( e.type ) {
                case SDL_KEYDOWN: {
                    if( e.key.keysym.sym == SDLK_ESCAPE ) m_Running = false;

                    // joypad #1
                    if( e.key.keysym.sym == SDLK_t )      m_Joypad_1->SetInput( BUTTON_A );
                    if( e.key.keysym.sym == SDLK_y )      m_Joypad_1->SetInput( BUTTON_B );
                    if( e.key.keysym.sym == SDLK_LSHIFT ) m_Joypad_1->SetInput( BUTTON_SELECT );
                    if( e.key.keysym.sym == SDLK_LCTRL )  m_Joypad_1->SetInput( BUTTON_START );
                    if( e.key.keysym.sym == SDLK_w )      m_Joypad_1->SetInput( BUTTON_UP );
                    if( e.key.keysym.sym == SDLK_s )      m_Joypad_1->SetInput( BUTTON_DOWN );
                    if( e.key.keysym.sym == SDLK_a )      m_Joypad_1->SetInput( BUTTON_LEFT );
                    if( e.key.keysym.sym == SDLK_d )      m_Joypad_1->SetInput( BUTTON_RIGHT );
                } break;

                case SDL_KEYUP: {
                    if( e.key.keysym.sym == SDLK_ESCAPE ) m_Running = false;

                    // joypad #1
                    if( e.key.keysym.sym == SDLK_t )      m_Joypad_1->ClearInput( BUTTON_A );
                    if( e.key.keysym.sym == SDLK_y )      m_Joypad_1->ClearInput( BUTTON_B );
                    if( e.key.keysym.sym == SDLK_LSHIFT ) m_Joypad_1->ClearInput( BUTTON_SELECT );
                    if( e.key.keysym.sym == SDLK_LCTRL )  m_Joypad_1->ClearInput( BUTTON_START );
                    if( e.key.keysym.sym == SDLK_w )      m_Joypad_1->ClearInput( BUTTON_UP );
                    if( e.key.keysym.sym == SDLK_s )      m_Joypad_1->ClearInput( BUTTON_DOWN );
                    if( e.key.keysym.sym == SDLK_a )      m_Joypad_1->ClearInput( BUTTON_LEFT );
                    if( e.key.keysym.sym == SDLK_d )      m_Joypad_1->ClearInput( BUTTON_RIGHT );
                } break;
            }
        }
        
        // FIXME
        if( m_PPU->WantRender() ) {
            Render( m_PPU->GetScreenData() );
        }
    }
}*/

////////////////////////////////////////////////////////////////////////////////////////////////////

void NES::Render( unsigned long* data ) {
    if( SDL_MUSTLOCK(m_Screen) ){
        if( SDL_LockSurface(m_Screen) < 0 ) return;
    }

    unsigned char* pixels = ( unsigned char* )m_Screen->pixels;
    unsigned short pitch  = m_Screen->pitch;
    unsigned char bpp     = m_Screen->format->BytesPerPixel;

    int x = 0, y = 0;
    for( int i = 0; i < 256 * 240; i++ ) {
        unsigned char* p0 = pixels + y * pitch + x * bpp;
        unsigned char* p1 = pixels + y * pitch + (x + 1) * bpp;
        unsigned char* p2 = pixels + (y + 1) * pitch + x * bpp;
        unsigned char* p3 = pixels + (y + 1) * pitch + (x + 1) * bpp;
        *( unsigned long* )p0 = data[ i ];
        *( unsigned long* )p1 = data[ i ];
        *( unsigned long* )p2 = data[ i ];
        *( unsigned long* )p3 = data[ i ];
        x += 2;
        if( x % 512 == 0 ) {
            x = 0;
            y += 2;
        }
    }

    SDL_Flip( m_Screen );
    if( SDL_MUSTLOCK(m_Screen) ) SDL_UnlockSurface( m_Screen );
}

////////////////////////////////////////////////////////////////////////////////////////////////////
